//
//  SCSock.swift
//  SecretChat
//
//  Created by TimTiger on 16/6/23.
//  Copyright © 2016年 TimTiger. All rights reserved.
//

import Foundation
import Security.SecureTransport
import Darwin

private var socketConnection: Int32 = 0

public class SCSockClient: NSObject {
    
    var hostAddr: String = ""
    var port: UInt16 = 0
    var context: SSLContextRef?
    var connectResult: OSStatus = -50
    var closeResult: OSStatus = -50
    private var shakeCount: Int = 1 << 26
    
    public init(addr: String,port: UInt16){
        self.hostAddr=addr
        self.port=port
    }
    
    //MARK: - Public
    /**
     是否已经连接服务
     */
    public func isConnected()->Bool {
        return (self.connectResult == errSecSuccess)
    }
    
    /**
     连接推送服务
     */
    public func connectionServer() throws {
        
        self.disconnect()
        
        self.connectResult = self.connectSocket()
        if self.connectResult != errSecSuccess {
            throw(NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "Socket Connect Failed"]))
        }
        
        // Create new SSL context.
        self.context = SSLCreateContext(kCFAllocatorDefault, SSLProtocolSide.ClientSide, SSLConnectionType.StreamType)
        
        // Set callback functions for SSL context.
        self.connectResult = SSLSetIOFuncs(self.context!,sslReadCallBack, sslWriteCallBack);
        if self.connectResult != errSecSuccess {
            throw(NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "IO Set Failed"]))
        }
        
        // Set SSL context connection.
        self.connectResult = SSLSetConnection(self.context!, &socketConnection)
        if self.connectResult != errSecSuccess {
            throw(NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "SSL Connection Set Failed"]))
        }
        
        // Set server domain name.
        self.connectResult = SSLSetPeerDomainName(self.context!, self.hostAddr, Int(strlen(self.hostAddr)));
        if self.connectResult != errSecSuccess {
            throw(NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "Peer name set error"]))
        }
        
        let p12Path = NSBundle.mainBundle().pathForResource("secretchat", ofType: "p12")
        let certificateData = NSData(contentsOfFile: p12Path!)
    
        var keyref: CFArray?
        self.connectResult = SecPKCS12Import(certificateData!, [(kSecImportExportPassphrase as String) : "123456"] as CFDictionaryRef , &keyref)
        let theArray: CFArray = keyref!
        if self.connectResult == noErr && CFArrayGetCount(keyref!) > 0 {
            let newArray = theArray as NSArray
            let dictionary = newArray.objectAtIndex(0)
            let secIdentity = dictionary.valueForKey(kSecImportItemIdentity as String) as! SecIdentityRef
            let certs = NSArray(objects: secIdentity) as CFArray
            self.connectResult = SSLSetCertificate(self.context!, certs)
            if self.connectResult != errSecSuccess {
                throw(NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "Certificate set error"]))
            }
        }
  
        self.connectResult = errSSLWouldBlock
        
        for _ in 0 ..< self.shakeCount {
            self.connectResult = SSLHandshake(self.context!);
            if self.connectResult != errSSLWouldBlock {
                break
            }
        }
    
        switch self.connectResult {
        case errSecSuccess:
            break
        default:
            throw(NSError(domain: "error", code: Int(self.connectResult), userInfo: [NSLocalizedDescriptionKey: "Shake error"]))
        }
        
    }
    
    /**
     断开连接
     */
    public func disconnect() {
        if (self.connectResult != 0) {
            return
        }
        self.closeResult = SSLClose(self.context!)
        self.context = nil
    }
    
    /**
     写入
     
     - parameter data: 数据
     
     - returns: 是否成功
     */
    public func writeData(data: NSData)->NSError? {
        
        if self.connectResult == -50 {
            return NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "SSL Lost Connection"])
        }
        
        var processed: Int = 0
        let status = SSLWrite(self.context!, data.bytes, data.length, &processed)
        if processed != data.length {
            return NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "SSL Write Error"])
        }
        switch status {
        case errSecSuccess:
            return nil
        default:
            return NSError(domain: "error", code: 400, userInfo: [NSLocalizedDescriptionKey: "SSL Write Error"])
        }
    }
    
    
    //MARK: - Private func
    /**
     连接socket服务
     
     - returns: 状态
     */
    private func connectSocket()->OSStatus {
        let sock = socket(AF_INET, SOCK_STREAM, 0)
        if sock < 0 {
            return OSStatus(-1)
        }
        let entr: hostent! = gethostbyname(self.hostAddr).memory
        if entr == nil {
            return OSStatus(-1)
        }
        var addr_in = sockaddr_in()
        var host: in_addr = in_addr()
        memcpy(&host, entr.h_addr_list[0],sizeof(in_addr))
        addr_in.sin_addr = host
        addr_in.sin_port =  _OSSwapInt16(self.port)
        addr_in.sin_family = UInt8(AF_INET)
        var addr: sockaddr = unsafeBitCast(addr_in,sockaddr.self)
        let conn = connect(sock, &addr, UInt32(sizeof(sockaddr_in)))
        if conn < 0 {
            return OSStatus(-1)
        }
        let cntl = fcntl(sock, F_SETFL, O_NONBLOCK)
        if cntl < 0 {
            return OSStatus(-1)
        }
        var set: Int32 = 1
        let sopt = setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &set, UInt32(sizeof(Int32)))
        if sopt < 0 {
            return OSStatus(-1)
        }
        socketConnection = sock
        return noErr
    }
    
    

}

/// IO funcs: in
func sslReadCallBack(connection: SSLConnectionRef,data: UnsafeMutablePointer<Void>,dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
    let leng: size_t = dataLength.memory
    var read: size_t = 0
    var rcvd: ssize_t = 0
    while (read < leng) {
        rcvd = recv( UnsafePointer<Int32>(connection).memory, data+read, leng - read,0)
        if rcvd <= 0 {
            break
        }
        read += rcvd
    }
    dataLength.memory = read
    if (rcvd > 0 || leng == 0) {
        return errSecSuccess;
    }
    if (rcvd == 0) {
        return errSSLClosedGraceful;
    }
    switch errno {
    case EAGAIN:
        return errSSLWouldBlock
    case ECONNRESET:
        return errSSLClosedAbort
    default:
        return errSecIO
    }
    
}

/// IO funcs: out
func sslWriteCallBack(connection: SSLConnectionRef,data: UnsafePointer<Void>,dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
    
    let leng: size_t = dataLength.memory
    var sent: size_t = 0
    var wrtn: ssize_t = 0
    while (sent < leng) {
        wrtn = write(socketConnection, data, leng-sent)
        if wrtn <= 0 {
            break
        }
        sent += wrtn
    }
    dataLength.memory = sent
    if wrtn > 0 || leng == 0 {
        return errSecSuccess
    }
    switch errno {
    case EAGAIN:
        return errSSLWouldBlock
    case EPIPE:
        return errSSLClosedAbort
    default:
        return errSecIO
    }
}

